我们前面学习了 Vite 的各种高级应用场景,接下来的几个小节,我们再把目光放到 Vite 的实现本身,来深度剖析 Vite 的内部源码实现。
可能你会有一个疑问,我们为什么要去读源码?原因主要有两个:一是加深对框架本身的理解,在面对一些项目的疑难杂症时,排查问题效率会更高;二是在遇到类似的开发场景时,可以举一反三,借鉴某个框架源码的实现思路,将技巧应用到其它的项目中。
本小节我们要介绍 Vite 配置解析服务的源码部分。我们知道,Vite 构建环境分为开发环境和生产环境,不同环境会有不同的构建策略,但不管是哪种环境,Vite 都会首先解析用户配置。那接下来,我就与你分析配置解析过程中 Vite 到底做了什么。
首先,我会带你梳理整体的实现流程,然后拆解其中的重点细节,即如何加载配置文件,让你不仅对 Vite 的配置解析服务有系统且完整的认识,还能写一个自己的配置文件加载器。
# 流程梳理
我们先来梳理整体的流程,Vite 中的配置解析由 resolveConfig 函数来实现,你可以对照源码一起学习。
# 1. 加载配置文件
进行一些必要的变量声明后,我们进入到解析配置逻辑中:
// 这里的 config 是命令行指定的配置,如 vite --configFile=xxx
let { configFile } = config
if (configFile !== false) {
// 默认都会走到下面加载配置文件的逻辑,除非你手动指定 configFile 为 false
const loadResult = await loadConfigFromFile(
configEnv,
configFile,
config.root,
config.logLevel
)
if (loadResult) {
// 解析配置文件的内容后,和命令行配置合并
config = mergeConfig(loadResult.config, config)
configFile = loadResult.path
configFileDependencies = loadResult.dependencies
}
}
第一步是解析配置文件的内容(这部分比较复杂,本文后续单独分析),然后与命令行配置合并。值得注意的是,后面有一个记录configFileDependencies的操作。因为配置文件代码可能会有第三方库的依赖,所以当第三方库依赖的代码更改时,Vite 可以通过 HMR 处理逻辑中记录的configFileDependencies检测到更改,再重启 DevServer ,来保证当前生效的配置永远是最新的。
# 2. 解析用户插件
第二个重点环节是 解析用户插件。首先,我们通过 apply 参数 过滤出需要生效的用户插件。为什么这么做呢?因为有些插件只在开发阶段生效,或者说只在生产环境生效,我们可以通过 apply: 'serve' 或 'build' 来指定它们,同时也可以将apply配置为一个函数,来自定义插件生效的条件。解析代码如下:
// resolve plugins
const rawUserPlugins = (config.plugins || []).flat().filter((p) => {
if (!p) {
return false
} else if (!p.apply) {
return true
} else if (typeof p.apply === 'function') {
// apply 为一个函数的情况
return p.apply({ ...config, mode }, configEnv)
} else {
return p.apply === command
}
}) as Plugin[]
// 对用户插件进行排